﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Transactions;
using System.Xml.Linq;
using BlueCurve.Common;
using BlueCurve.Common.Transaction;
using BlueCurve.WebCrawlerRequest;
using CrawlingApplication.Db;
using Microsoft.Win32.SafeHandles;


namespace CrawlingApplication
{
    /// <summary>
    /// Class de traitement des données
    /// </summary>
    public class Treatment : BlueCurve.Common.Transaction.FileGenerationService.FileGeneration
    {
        #region 'Configuration'

        /// <summary>
        /// Liste contenant les données en cours de traitenement
        /// </summary>
        private Queue m_datas = new Queue();
        /// <summary>
        /// Permet au thread de savoir si il doit reste en vie
        /// </summary>
        private bool m_IsRun = true;
        /// <summary>
        /// Nombre de threads encore en vie
        /// </summary>
        private long m_AliveThreads = 1;
        /// <summary>
        /// Chemin ou écrire les fichiers pour les scheduler
        /// </summary>
        private string m_SchedulerQueuepath = ConfigurationManager.AppSettings["SchedulerQueuePath"];
        /// <summary>
        /// Objet de synchronisation
        /// </summary>
        private readonly object m_ThreadsLock = new object();
        /// <summary>
        /// Evement pour la demande de nouvelles données à parser
        /// </summary>
        public event EventHandler GetFilesToParse;
        /// <summary>
        /// Evenement fichier à supprimer de la base de données
        /// </summary>
        public delegate void EventRaiseDelefile(object sender, EventData e);
        public event EventRaiseDelefile Delefile;

        /// <summary>
        /// Permet de stopper le thread
        /// </summary>
        public bool StopRun
        {
            set { this.m_IsRun = false; }
        }

        /// <summary>
        /// Obtien le nombre de threads
        /// en cours
        /// </summary>
        public long AliveThreads
        {
            get { return Interlocked.Read(ref this.m_AliveThreads); }
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        public Treatment()
        {
            if (!Directory.Exists(this.m_SchedulerQueuepath))
                Directory.CreateDirectory(this.m_SchedulerQueuepath);
        }

        #endregion



        #region 'Functions'

        /// <summary>
        /// Fonction principale de la class
        /// </summary>
        public void RunTreatment()
        {
            this.Reload();

            while (this.m_IsRun)
            {
                lock (this.m_datas.SyncRoot)
                {
                    if (this.m_datas.Count == 0)
                    {
                        using (CrawlingApplicationdbDataContext db = new CrawlingApplicationdbDataContext())
                        {
                            Nullable<int> count = db.CountPath();

                            if (count.HasValue && count.Value == 0)
                                GetFilesToParse("", EventArgs.Empty);
                            else
                                this.m_datas = new Queue(db.GetPath(Environment.ProcessorCount * 25).ToList());
                        }
                    }
                    else
                    {
                        while(this.m_datas.Count > 0)
                        {
                            ThreadPool.QueueUserWorkItem(new WaitCallback(DataTreatment), this.m_datas.Dequeue());
                        }
                    }
                }

                Thread.Sleep(60 * 1000);
            }

            while (this.AliveThreads > 1) { Thread.Sleep(150); }
            if (this.m_datas.Count > 0)
                this.Save();

            Interlocked.Decrement(ref this.m_AliveThreads);
        }

        #endregion



        #region 'Treatment'

        /// <summary>
        /// Fonction attaché au pool de threads
        /// </summary>
        /// <param name="sender">chemin de fichier</param>
        private void DataTreatment(object sender)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            CommonFunctions Common = new CommonFunctions();
            string path = sender as string;
            try
            {
                if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
                    return;

                string Url = null;
                string Location = null;
                XDocument doc = XDocument.Load(string.Concat(path, @"\info.xml"));
                var x = from i in doc.Elements()
                        select new
                        {
                            Uri = i.Element("url").Value,
                            Location = i.Element("location").Value
                        };

                foreach (var z in x)
                {
                    Url = z.Uri;
                    Location = z.Location;
                }

                if (string.IsNullOrEmpty(Url))
                    throw new Exception("Url ne peut pas être vide");
                else
                    Url = Common.NormalizeLink(Url);

                if (!string.IsNullOrEmpty(Location))
                    Location = Common.NormalizeLink(Location);

                // Désérialisation des données
                RequestState rs = this.ReadFile(string.Concat(path, @"\requeststate.bin"));
                // Ecriture d'un fichier temporaire
                base.Generate(rs.GetBuffer(), string.Concat(path, @"\data.bin"));
                
                // Extractions des urls
                List<string> Urls = Common.GetHyperTextLinks(string.Concat(path, @"\data.bin"));
                List<object> QUrls = new List<object>();

                // Url en cours
                string CurrentUrl = null;
                if (!string.IsNullOrEmpty(Location))
                    CurrentUrl = Location;
                else
                    CurrentUrl = Url;

                Urls.ForEach(
                     delegate(string url)
                     {
                         string temp = Common.NormalizeLink(CurrentUrl, url);
                         if (string.IsNullOrEmpty(temp) && !temp.Equals(Url) && !temp.Equals(Location))
                             QUrls.Add(new XElement("uri", new XCData(temp)));
                     });

                // Construction d'un fichier XML pour le scheduler et sauvegarde sur le disque
                XElement xml = new XElement("node",
                               new XElement("urlbase", new XCData(string.IsNullOrEmpty(Location) ? Url : Location)),
                               new XElement("urls", QUrls.ToArray()));

                StringWriter sw = new StringWriter();
                xml.Save(sw);
               
                // Chemin ou écrire le fichier de réponse
                string savepath = Path.GetFullPath(string.Concat(this.m_SchedulerQueuepath, @"\", Common.GetLastPathElementFromPath(path), Guid.NewGuid().ToString(), ".xml"));
                // Ecriture du fichier de réponse
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0, 1, 40)))
                {
                    FileGenerationService instanceFile = new FileGenerationService();
                    using (TransactionScope statetx = new TransactionScope(TransactionScopeOption.RequiresNew))
                    {
                        instanceFile.Generate(sw.ToString(), savepath);
                        statetx.Complete();
                    }
                    scope.Complete();
                }
              
                // Ajoute le chemin du fichier dans la pile de traitement
                // pour le scheduler
                TreatmentSchedule.GetInstance.Add(savepath);
                
                QUrls.Clear();
                Urls.Clear();
            } catch {
            }
            finally
            {
                // Déclenche l'évènement permettant de supprimer un chemin de fichier de la base de données
                lock (this.m_ThreadsLock)
                {
                    Delefile("", new EventData() { path = path });
                }

                if (File.Exists(string.Concat(path, @"\data.bin")))
                    File.Delete(string.Concat(path, @"\data.bin"));
            }
            Interlocked.Decrement(ref this.m_AliveThreads);
        }



        /// <summary>
        /// Désérialise l'objet de la requete
        /// </summary>
        /// <param name="path">Chemin d'accès du fichier</param>
        /// <returns>RequestState</returns>
        new public RequestState ReadFile(string path)
        {
            RequestState rs = null;
            SafeTransactionHandle txHandle = null;
            SafeFileHandle fileHandle = null;

            try
            {
                IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                kernelTx.GetHandle(out txHandle);

                fileHandle
                    = CreateFileTransacted(
                        path
                        , SafeTransactionHandle.FileAccess.GENERIC_READ
                        , SafeTransactionHandle.FileShare.FILE_SHARE_READ
                        , IntPtr.Zero
                        , SafeTransactionHandle.FileMode.OPEN_ALWAYS
                        , 0
                        , IntPtr.Zero
                        , txHandle
                        , IntPtr.Zero
                        , IntPtr.Zero);

                if (fileHandle.IsInvalid)
                    throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

                using (FileStream stream = new FileStream(fileHandle, FileAccess.Read, 1024, false))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    rs = (RequestState)formatter.Deserialize(stream);
                    stream.Close();
                }

            }
            catch (Exception e)
            {
                
            }
            finally
            {
                if (fileHandle != null && !fileHandle.IsInvalid)
                {
                    fileHandle.Close();
                    fileHandle.Dispose();
                }

                if (txHandle != null && !txHandle.IsInvalid)
                {
                    txHandle.Close();
                    txHandle.Dispose();
                }
            }
            return rs;
        }

        

        /// <summary>
        /// Sauvegarde la pile d'indexation
        /// </summary>
        private void Save()
        {
            using (FileStream stream = File.Open("TreatmentQueue.bin", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this.m_datas);
                stream.Close();
            }
        }



        /// <summary>
        /// Recharge la pile d'indexation
        /// </summary>
        private void Reload()
        {
            if (File.Exists("TreatmentQueue.bin"))
            {
                System.Collections.Queue queue;
                using (FileStream stream = File.Open("TreatmentQueue.bin", FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    queue = (System.Collections.Queue)formatter.Deserialize(stream);
                    stream.Close();
                }

                while (queue.Count > 0)
                {
                    this.m_datas.Enqueue((string)queue.Dequeue());
                }

                File.Delete("TreatmentQueue.bin");
            }
        }

        #endregion

        public class EventData : EventArgs
        {
            public string path { get; set; }
        }
    }
}
